package com.yunkeji.api.msg.service.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;

import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.cache.LoadingCache;
import com.yunkeji.api.common.service.SysParamService;
import com.yunkeji.api.msg.bo.SendBo;
import com.yunkeji.api.msg.service.MsgService;
import com.yunkeji.api.user.mapper.ApiAppUserMapper;
import com.yunkeji.api.user.mapper.ApiSysHelloDetailMapper;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.constant.SysConfigConsts;
import com.yunkeji.common.constant.SysIdConstant;
import com.yunkeji.common.core.domain.dto.PayLogDTO;
import com.yunkeji.common.enums.app.*;
import com.yunkeji.common.exception.AppServiceException;
import com.yunkeji.common.helper.LoginHelper;
import com.yunkeji.common.msg.PushMessage;
import com.yunkeji.common.msg.bo.PrivateMsg;
import com.yunkeji.common.msg.consts.MsgConstant;
import com.yunkeji.common.msg.utils.BadWordDetectorUtils;
import com.yunkeji.common.utils.StringUtils;
import com.yunkeji.common.utils.redis.RedisUtils;
import com.yunkeji.dao.domain.*;
import com.yunkeji.dao.mapper.*;

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Future;

import javax.annotation.Resource;

/**
 * @author 老张
 */
@Slf4j
@Service
@AllArgsConstructor
public class MsgServiceImpl implements MsgService {
    private final ApiAppUserMapper apiAppUserMapper;
    private final UserBlackListMapper userBlackListMapper;
    private final SysParamService sysParamService;
    private final AppUserExtMapper appUserExtMapper;
    private final PayLogMapper payLogMapper;
    private final AppUserWalletMapper appUserWalletMapper;
    private final ApiSysHelloDetailMapper apiSysHelloDetailMapper;
    private final AppUserVipMapper appUserVipMapper;
    private final AppUserSpecialMapper specialMapper;

    @Resource(name = "sensitiveWordCheck")
    LoadingCache<String, BadWordDetectorUtils> sensitiveWordCheck;

    /**
     * 发送消息
     *
     * @author 老张
     * @param bo
     */
    @Override
    @SneakyThrows
    @Lock4j(
            name = CacheConstants.LOCK_SEND_MSG,
            keys = {"#bo.lockId"})
    public String send(SendBo bo) {
        MsgTypeEnums msgType = MsgTypeEnums.getByCode(bo.getMsgType());

        AppUserExt sendAppUserExt = appUserExtMapper.selectById(LoginHelper.getUserId());
        AppUser sendAppUser = apiAppUserMapper.selectById(LoginHelper.getUserId());
        AppUser recAppUser = apiAppUserMapper.selectById(bo.getRecUserId());

        if (recAppUser == null || !recAppUser.getStatus().equals(AppUserStatus.正常.getCode())) {
            throw AppServiceException.accountException("对方账号异常");
        }

        if(sendAppUser.getSex().equals(SexEnum.女.getCode())){
            String key = CacheConstants.MSG_TIME_LIMIT + recAppUser.getPlatformId() + ":" + sendAppUser.getPlatformId();
            String str = RedisUtils.getCacheObject(key);
            if (StringUtils.isEmpty(str)) {
                boolean flag = addMsgLimit(sendAppUser.getPlatformId(), recAppUser.getPlatformId());
                if(!flag){
                    throw AppServiceException.accountException("对方回复你之前，只可发送3条文字消息，请礼貌发言并自觉遵守平台公约。");
                }
            }
        } else {
            delMsgLimit(recAppUser.getPlatformId(), sendAppUser.getPlatformId());
            // 设置24聊天标识，代表该女用户给该男用户发送消息没有限制
            String key = CacheConstants.MSG_TIME_LIMIT + sendAppUser.getPlatformId() + ":" + recAppUser.getPlatformId();
            RedisUtils.setCacheObject(key, "1", Duration.ofDays(1));
        }

        if (msgType.equals(MsgTypeEnums.消息收条.getCode())) {
            log.info("发送收条消息");
            PushMessage.sendReceipt(
                    sendAppUser.getPlatformId(),
                    recAppUser.getPlatformId(),
                    JSONUtil.parseObj(bo.getContent()));
            return StrUtil.EMPTY;
        }
        // 检查状态
        checkMsgSend(sendAppUser, recAppUser, bo);

        // 已经免费发送消息的次数
        // Integer freeMsgCount = sendAppUserExt.getFreeMsgCount();

        // #378 2、对所有用户，每日免费消息10条（对方回复后正常交流算一条）
        Date date = new Date();
        // 避免出现过期时间为0秒的情况，导致运行redis脚本时报错
        long restS = DateUtil.between(date, DateUtil.endOfDay(date), DateUnit.SECOND) + 1;
        // 最大免费发送的次数
        Integer maxNum =
                Integer.valueOf(sysParamService.selectConfigByKey(SysConfigConsts.最大免费文字消息次数));
        Boolean isFreeMsg =
                RedisUtils.setAtomicValueAdd(
                        CacheConstants.MSG__FREE_LIMIT + LoginHelper.getUserId(),
                        maxNum,
                        Integer.parseInt(String.valueOf(restS)));

        // 是否是vip
        AppUserVip appUserVip = appUserVipMapper.selectById(sendAppUserExt.getId());
        Boolean isVip =
                appUserVip.getCommonVipFlag().equals(EnableEnum.YES.getCode())
                        && appUserVip.getCommonVipExpireTime().getTime() >= new Date().getTime();
        // 是否免费
        Boolean isFree =
                // 1.男号-女号真人认证之后都不收费,
                sendAppUser.getVideoAuth().equals(CheckStatus.审核通过.getCode())
                        // 2.已经免费发送的次数<最大免费消息次数(账号注册开始->账号注销)
                        //                        || freeMsgCount < maxNum
                        || isFreeMsg
                        // 3.开通会员可以免费文字
                        || isVip;
        if (!isFree) {
            // 消息收费
            msgFree(bo.getRecUserId(), sendAppUser, recAppUser);
        }
        //        if (freeMsgCount < maxNum) {
        //            // 更新免费消息次数 Monster:来个霸王条款，只限时使用，过期不候
        //            appUserExtMapper.updateById(
        //                    new AppUserExt()
        //                            .setId(sendAppUserExt.getId())
        //                            .setFreeMsgCount(sendAppUserExt.getFreeMsgCount() + 1));
        //        }

        PushMessage.sendTextSinglePrivate(
                sendAppUser.getPlatformId(),
                recAppUser.getPlatformId(),
                PrivateMsg.builder()
                        .content(bo.getContent())
                        .nickname(sendAppUserExt.getNickName())
                        .headUrl(sendAppUserExt.getHeadUrl())
                        .id(sendAppUser.getId())
                        .platformId(sendAppUser.getPlatformId())
                        .build());
        // 更新回复状态
        updateReplyStatus(sendAppUser, recAppUser);
        // 增加亲密度
        addLoveValue(sendAppUser, recAppUser);
        // 这个是去除关键词的
        return bo.getContent();
    }

    /**
     * 用户到客服
     *
     * @author 老张
     */
    @Override
    public String userToService(SendBo bo) {
        AppUser appUser =
                apiAppUserMapper.selectOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .eq(AppUser::getStatus, AppUserStatus.正常.getCode())
                                .eq(AppUser::getAccountType, AccountTypeEnums.客服.getCode()),
                        false);
        if (appUser == null) {
            throw AppServiceException.paramException("暂无可用客服");
        }
        // 消息内容过滤
        // checkMsgContent(bo);
        AppUserExt appUserExt = appUserExtMapper.selectById(LoginHelper.getUserId());
        PushMessage.userToService(
                LoginHelper.getPlatformId(),
                PrivateMsg.builder()
                        .id(LoginHelper.getUserId())
                        .platformId(LoginHelper.getPlatformId())
                        .headUrl(appUserExt.getHeadUrl())
                        .content(bo.getContent())
                        .nickname(appUserExt.getNickName())
                        .build());
        return bo.getContent();
    }

    /**
     * 客服到用户
     *
     * @author 老张
     */
    @Override
    public String serviceToUser(SendBo bo) {
        Integer accountType = apiAppUserMapper.selectById(LoginHelper.getUserId()).getAccountType();
        if (!accountType.equals(AccountTypeEnums.客服.getCode())) {
            throw AppServiceException.paramException("不可发送客服消息");
        }

        AppUser recAppUser = apiAppUserMapper.selectById(bo.getRecUserId());

        PushMessage.serviceToUser(
                LoginHelper.getPlatformId(), recAppUser.getPlatformId(), bo.getContent());
        return bo.getContent();
    }

    /**
     * 用户打开在线客服后的自动提醒
     *
     * @author 雷擎宇
     */
    @Async
    @Override
    public void kefuRemindUser(SendBo bo) {
        AppUser recAppUser = apiAppUserMapper.selectById(bo.getRecUserId());
        //延迟五秒发送消息
        try {
            Thread.sleep(5000);
            PushMessage.sendTextSingle(
                SysIdConstant.客服,
                recAppUser.getPlatformId(),
                "您好，请问有什么能帮到您？");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 文字聊天-收费 私信价格设置,男性给女性发消息需要收费,价格后台配置,女性认证后消息免费 好,免费发几条消息后台配置 文字消息收费
     *
     * @author 老张
     */
    private void msgFree(Long recUid, AppUser sendAppUser, AppUser recAppUser) {
        // 单次消息费用
        BigDecimal singlePrice =
                new BigDecimal(sysParamService.selectConfigByKey(SysConfigConsts.文字消息费用));
        AppUserWallet appUserWallet = appUserWalletMapper.selectById(LoginHelper.getUserId());
        if (NumberUtil.isLess(appUserWallet.getGoldBalance(), singlePrice)) {
            throw AppServiceException.goldNotEnoughException("免费发送消息余额不足,请充值或者开通会员后再试。");
        }
        // 扣除发送人的金币

        Integer integer =
                apiAppUserMapper.logSubGoldBalance(
                        PayLogDTO.builder()
                                .uid(LoginHelper.getUserId())
                                .changeNum(singlePrice)
                                .serviceTag("用户发送文字消息-接收人:" + recAppUser.getPlatformId())
                                .logType(PayLogTypeEnums.消费_文字聊天消费)
                                .build());
        if (integer != 1) {
            throw AppServiceException.goldNotEnoughException("免费发送消息余额不足,请充值或者开通会员后再试。");
        }

        apiAppUserMapper.logAddDiamondBalance(
                PayLogDTO.builder()
                        .uid(recUid)
                        .changeNum(singlePrice)
                        .serviceTag("用户接收文字消息-发送人:" + sendAppUser.getPlatformId())
                        .logType(PayLogTypeEnums.收益_文字聊天收益)
                        .build());
    }

    /**
     * 添加亲密度
     *
     * @author 老张
     */
    private void addLoveValue(AppUser sendAppUser, AppUser recAppUser) {
        UserFocus build =
                UserFocus.builder()
                        // 粉丝=发送者
                        .fansUserId(sendAppUser.getId())
                        .fansPlatformId(sendAppUser.getPlatformId())
                        // 关注者=接收者
                        .focusUserId(recAppUser.getId())
                        .focusPlatformId(recAppUser.getPlatformId())
                        .build();
        // 设置id
        build.setSortId(build.getSortIdStr());
        UserFocus userFocus =
                build.selectOne(
                        Wrappers.lambdaQuery(UserFocus.class)
                                .eq(UserFocus::getSortId, build.getSortIdStr()));
        if (userFocus == null) {
            // 不存在,说明未创建,则创建
            build.setDelFlag(EnableEnum.YES.getCode());
            build.insert();
        }
        // 更新亲密度
        apiAppUserMapper.addLoveValue(build.getSortId(), BigDecimal.ONE);
    }

    /**
     * 修改回复状态AppUserInfoServiceImpl
     *
     * @author 老张
     */
    private void updateReplyStatus(AppUser sendUser, AppUser recUser) {

        delMsgLimit(recUser.getPlatformId(), sendUser.getPlatformId());
        // 检查打招呼
        LambdaQueryWrapper<SysHelloDetail> eq =
                Wrappers.lambdaQuery(SysHelloDetail.class)
                        .eq(SysHelloDetail::getReply, EnableEnum.NO.getCode())
                        .eq(SysHelloDetail::getRecUid, sendUser.getPlatformId())
                        .eq(SysHelloDetail::getSendUid, recUser.getPlatformId());
        SysHelloDetail sysHelloDetail = SysHelloDetail.builder().build().selectOne(eq);
        if (sysHelloDetail != null) {
            sysHelloDetail.update(
                    Wrappers.lambdaUpdate(SysHelloDetail.class)
                            .eq(SysHelloDetail::getSendUid, recUser.getPlatformId())
                            .eq(SysHelloDetail::getRecUid, sendUser.getPlatformId())
                            .set(SysHelloDetail::getReply, EnableEnum.YES.getCode()));
        }
    }

    /**
     * 检查状态
     *
     * @author 老张
     * @param sendUser
     * @param recUser
     */
    @SneakyThrows
    private void checkMsgSend(AppUser sendUser, AppUser recUser, SendBo bo) {
        if (recUser == null || !recUser.getStatus().equals(AppUserStatus.正常.getCode())) {
            throw AppServiceException.accountException("对方账号异常");
        }
        if (sendUser.getSex().equals(recUser.getSex())) {
            // @有舍有得 @Monsters(sudo rm -rf) 搜同性别?同性别也可以打电话吗?还是只是搜出来?还有打招呼,礼物.这些.能同性别交互?
            // 不能 只能搜不能打 可以送礼物 其他都不行
            throw AppServiceException.accountException("暂不可给对方发消息");
        }

        // 为避免骚扰，需要对方回复或24小时后您才能继续给对方发送消息。
        // 20240927 fanson 关掉
        //        Boolean rate = addMsgLimit(sendUser.getPlatformId(), recUser.getPlatformId());
        //        if (!rate) {
        //            throw AppServiceException.paramException("为避免骚扰，需要对方回复或24小时后您才能继续给对方发送消息。");
        //        }
        UserBlackList userBlackList =
                userBlackListMapper.selectOne(
                        Wrappers.lambdaQuery(UserBlackList.class)
                                .eq(UserBlackList::getToUserId, sendUser.getId())
                                .eq(UserBlackList::getFromUserId, recUser.getId()));
        if (userBlackList != null) {
            throw AppServiceException.inBlackList("已经被拉黑");
        }
        // 特殊权限用户不需要消息内容过滤
        AppUserSpecial special = specialMapper.selectById(sendUser.getId());
        if(Objects.isNull(special)){
            // 消息内容过滤
            checkMsgContent(bo);
        }
        //        if (check) {
        //            throw AppServiceException.accountException("不允许发送相关关键词,请遵守平台规则");
        //        }

        //        SysHelloDetail helloDetail =
        //                apiSysHelloDetailMapper.getHelloDetail(recUser.getId(), sendUser.getId());

    }

    /**
     * 消息流检测
     *
     * @author 老张
     * @param senderId
     * @param recverId
     * @return
     */
    private Boolean addMsgLimit(long senderId, long recverId) {
        String key = CacheConstants.MSG_LIMIT + senderId + ":" + recverId;
        return RedisUtils.setAtomicValueAdd(key, 3, MsgConstant.时间_24H);
    }

    /**
     * 消息流检测
     *
     * @author 老张
     * @param senderId
     * @param recverId
     * @return
     */
    private Boolean delMsgLimit(long senderId, long recverId) {
        String key = CacheConstants.MSG_LIMIT + senderId + ":" + recverId;
        return RedisUtils.deleteObject(key);
    }

    /**
     * 消息内容检测
     *
     * @author 老张
     */
    @SneakyThrows
    private void checkMsgContent(SendBo bo) {
        String submitKey = CacheConstants.MSG_CHECK + SecureUtil.md5(bo.getContent());
        Object cacheObject = RedisUtils.getCacheObject(submitKey);
        if (cacheObject != null) {
            // fansonคิดถึง 比如某个人发的某条消息违规了，其他人发了同样的消息，直接比对 MD5 就能知道是违规消息，不需要再去检测了
            bo.setContent("***");
            return;
        }
        Set<String> badWords =
                sensitiveWordCheck.get(CacheConstants.SENSITIVE_WORD).getBadWords(bo.getContent());
        if (badWords.size() > 0) {
            // 不通过 命中关键词
            badWords.forEach(
                    item -> {
                        String replace = bo.getContent().replace(item, "*");
                        bo.setContent(replace);
                    });
            RedisUtils.setCacheObject(submitKey, bo.getContent(), Duration.ofMinutes(60 * 24));
        } else {
            // 通过 再次检测
            boolean checkAgain = BadWordDetectorUtils.checkAgain(bo.getContent());
            if (checkAgain) {
                // 加强检测失败直接替换成***
                bo.setContent("***");
                RedisUtils.setCacheObject(submitKey, bo.getContent(), Duration.ofMinutes(60 * 24));
            }
        }
    }
}
